package com.seehack.sample.springrest.httpclient;

import com.seehack.sample.springrest.httpclient.interceptor.AccessLogProcessor;
import com.seehack.sample.springrest.httpclient.interceptor.ConnectionManagerStatusInterceptor;
import com.seehack.sample.springrest.httpclient.interceptor.RequestConfigInterceptor;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;

@Configuration
public class HttpClientConfiguration {

    @Bean
    public PoolingHttpClientConnectionManager createConnectionManager() {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(100);
        cm.setDefaultMaxPerRoute(20);
        return cm;
    }

    @Bean
    public HttpRequestRetryHandler createRetryHandler() {
        return new HttpRequestRetryHandler() {
            public boolean retryRequest(IOException e, int executionCount, HttpContext context) {
                if (executionCount > 3) {
                    // Do not retry if over max retry count
                    return false;
                }
                if (e instanceof InterruptedIOException) {
                    // Timeout
                    return false;
                }
                if (e instanceof UnknownHostException) {
                    // Unknown host
                    return false;
                }
                if (e instanceof ConnectTimeoutException) {
                    // Connection refused
                    return false;
                }
                if (e instanceof SSLException) {
                    // SSL handshake exception
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // Retry if the request is considered idempotent
                return !(request instanceof HttpEntityEnclosingRequest);
            }
        };
    }

    @Bean
    public RequestConfig createHttpClientDefaultRequestConfig() {
        return RequestConfig.custom()
                .setConnectionRequestTimeout(5000)
                .setConnectTimeout(5000)
                .setSocketTimeout(5000)
                .build();
    }

    @Bean
    public ConnectionKeepAliveStrategy creatKeepAliveStrategy() {
        return new ConnectionKeepAliveStrategy() {

            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                HeaderElementIterator it = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (it.hasNext()) {
                    HeaderElement he = it.nextElement();
                    String param = he.getName();
                    String value = he.getValue();
                    if (value != null && param.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch (NumberFormatException ignore) {
                        }
                    }
                }
                return 5 * 1000;
            }
        };
    }

    @Bean
    public CloseableHttpClient createHttpClient(PoolingHttpClientConnectionManager cm,
                                                HttpRequestRetryHandler retryHandler,
                                                RequestConfig defaultRequestConfig,
                                                ConnectionKeepAliveStrategy keepAliveStrategy) {
        AccessLogProcessor accessLogProcessor = new AccessLogProcessor();
        ConnectionManagerStatusInterceptor connectionManagerStatusInterceptor =
                new ConnectionManagerStatusInterceptor(cm);
        RequestConfigInterceptor requestConfigInterceptor = new RequestConfigInterceptor();
        return HttpClients.custom()
                .setConnectionManager(cm)
                .setRetryHandler(retryHandler)
                .setDefaultRequestConfig(defaultRequestConfig)
                .setKeepAliveStrategy(keepAliveStrategy)
                .addInterceptorLast(requestConfigInterceptor)
                .addInterceptorLast((HttpRequestInterceptor) accessLogProcessor)
                .addInterceptorLast((HttpResponseInterceptor) accessLogProcessor)
                .addInterceptorLast(connectionManagerStatusInterceptor)
                .build();
    }

    @Bean
    public RequestConfigClientHttpRequestFactory createHttpComponentsClientHttpRequestFactory(CloseableHttpClient httpClient) {
        return new RequestConfigClientHttpRequestFactory(httpClient);
    }
}
